from flask import Flask, render_template, request, jsonify, session
from flask_socketio import SocketIO, emit
import os
import uuid
import time
import eventlet
eventlet.monkey_patch()
from werkzeug.utils import secure_filename
from config import Config
from services.image_service import process_image
from services.predict_service import predict_service
from datetime import datetime
import requests
import json
import logging
import threading
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import matplotlib

app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config['SECRET_KEY']
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')

# 启用Socket.IO日志
logging.basicConfig(level=logging.INFO)

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

# 确保输出目录存在
os.makedirs('output', exist_ok=True)

# 添加全局字典来存储任务信息
batch_tasks = {}
batch_tasks_lock = threading.Lock()  # 添加锁以防止并发访问问题

# 添加Socket.IO事件处理器，用于测试连接状态
@socketio.on('connect')
def handle_connect():
    print('Client connected')
    
@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')

@app.before_first_request
def check_model_file():
    """检查模型文件是否存在"""
    model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "best_resnet_model.pth")
    if not os.path.exists(model_path):
        error_msg = f"错误: 模型文件不存在: {model_path}"
        app.logger.error(error_msg)
        print("\n" + "="*80)
        print(error_msg)
        print("请确保将训练好的模型文件 'best_resnet_model.pth' 放在项目根目录下。")
        print("="*80 + "\n")
    else:
        app.logger.info(f"模型文件已找到: {model_path}")
        print(f"信息: 模型文件已找到: {model_path}")

@app.route('/')
def index():
    """渲染主页"""
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload_file():
    """处理图像上传"""
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    if file and allowed_file(file.filename):
        try:
            # 生成安全的文件名
            filename = secure_filename(file.filename)
            # 添加唯一标识符防止文件名冲突
            unique_filename = f"{uuid.uuid4().hex}_{filename}"
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
            file.save(filepath)
            
            # 检查是否为左右眼上传
            eye = request.form.get('eye', None)
            if eye == 'left':
                session['left_eye_image'] = unique_filename  # 修改：只存储文件名，不存储完整路径
            elif eye == 'right':
                session['right_eye_image'] = unique_filename  # 修改：只存储文件名，不存储完整路径
            else:
                # 默认单眼上传
                session['single_eye_image'] = unique_filename  # 修改：变量名从 uploaded_image 改为 single_eye_image
            
            # 返回上传成功的响应
            return jsonify({
                'success': True,
                'filename': unique_filename,
                'filepath': filepath
            })
        except Exception as e:
            return jsonify({'success': False, 'error': str(e)}), 500
    
    return jsonify({'error': 'File type not allowed'}), 400

@app.route('/analyze', methods=['POST'])
def analyze():
    """处理图像分析请求"""
    try:
        # 获取表单数据
        patient_id = request.form.get('patient_id')
        patient_name = request.form.get('patient_name')
        age = request.form.get('age')
        gender = request.form.get('gender')
        exam_date = request.form.get('exam_date')
        mode = request.form.get('mode')
        
        # 验证必要参数
        # if not all([patient_id, patient_name, age, gender, exam_date, mode]):
        #     return jsonify({"success": False, "message": "缺少必要参数"})
        
        # 构建患者信息
        patient_info = {
            "id": patient_id,
            "name": patient_name,
            "age": age,
            "gender": gender,
            "exam_date": exam_date
        }
        
        # 获取会话中的图像文件信息
        if mode == 'single':
            image_filename = session.get('single_eye_image')
            if not image_filename:
                return jsonify({"success": False, "message": "未找到单眼图像"})
            image_files = image_filename  # 只传递文件名
        else:  # mode == 'both'
            left_image = session.get('left_eye_image')
            right_image = session.get('right_eye_image')
            if not left_image or not right_image:
                return jsonify({"success": False, "message": "未找到左右眼图像"})
            image_files = {'left': left_image, 'right': right_image}  # 只传递文件名
        
        # 使用后台任务执行预测，避免请求超时
        socketio.start_background_task(
            run_analysis_task, mode, patient_info, image_files
        )
        
        # 立即返回成功响应，结果将通过WebSocket更新
        return jsonify({"success": True, "message": "分析已开始"})
            
    except Exception as e:
        # 发生异常，发送错误消息
        socketio.emit('analysis_update', {
            'status': 'error', 
            'message': f"分析过程中发生错误: {str(e)}"
        })
        return jsonify({"success": False, "message": f"分析过程中发生错误: {str(e)}"})

def run_analysis_task(mode, patient_info, image_files):
    """在后台线程中执行预测任务"""
    try:
        # 发送处理开始的消息
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 10})
        
        # 调用预测服务
        result = predict_service.predict(mode, patient_info, image_files)
        
        # 打印完整的结果数据以进行调试
        print("预测结果结构:", json.dumps(result, indent=2, ensure_ascii=False))
        
        # 更新进度到50%，表示预测完成
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 50})
        
        # 短暂延迟，确保前端能看到进度变化
        time.sleep(0.5)
        
        # 更新进度到90%，准备最终结果
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 90})
        
        # 确保结果结构一致且包含期望的字段
        if result.get("success"):
            # 检查诊断结果格式是否正确
            if "diagnosis" in result:
                # 如果诊断不包含class_probabilities，确保添加
                if isinstance(result["diagnosis"], dict) and "class_probabilities" not in result["diagnosis"]:
                    if "probabilities" in result:
                        result["diagnosis"]["class_probabilities"] = result["probabilities"]
            
            # 分析成功，发送完成消息
            print("预测成功，通过WebSocket发送结果")
            socketio.emit('analysis_update', {
                'status': 'completed', 
                'progress': 100,
                'results': result
            })
        else:
            # 分析失败，发送错误消息
            error_msg = result.get("message", "分析失败，未返回有效结果")
            socketio.emit('analysis_update', {
                'status': 'error', 
                'message': error_msg
            })
            
    except Exception as e:
        import traceback
        error_msg = f"分析任务执行失败: {str(e)}"
        print(error_msg)
        print(traceback.format_exc())
        
        # 发送错误消息
        socketio.emit('analysis_update', {
            'status': 'error', 
            'message': error_msg
        })

def run_single_analysis_task(image_path, patient_id, patient_name, age, gender, exam_date):
    """后台任务：处理单眼图像并运行模型"""
    try:
        # 发送开始处理的消息
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 0})
        
        # 处理图像
        processed_image = process_image(image_path)
        processed_image['mode'] = 'single'  # 设置模式为单眼
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 30})
        
        # 运行模型预测
        results = predict_disease(processed_image, {
            'patient_id': patient_id,
            'patient_name': patient_name,
            'age': age,
            'gender': gender,
            'exam_date': exam_date
        })
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 90})
        
        # 发送完成消息和结果
        socketio.emit('analysis_update', {
            'status': 'completed',
            'progress': 100,
            'results': results
        })
        
    except Exception as e:
        print(f"分析过程中发生错误: {str(e)}")
        # 发送错误消息
        socketio.emit('analysis_update', {
            'status': 'error',
            'message': str(e)
        })

def run_both_analysis_task(left_image_path, right_image_path, patient_id, patient_name, age, gender, exam_date):
    """后台任务：处理双眼图像并运行模型"""
    try:
        # 发送开始处理的消息
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 0})
        
        # 处理左眼图像
        processed_left = process_image(left_image_path)
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 20})
        
        # 处理右眼图像
        processed_right = process_image(right_image_path)
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 40})
        
        # 合并处理结果
        processed_image = {
            'original_path': left_image_path,  # 使用左眼作为主路径
            'left_path': left_image_path,
            'right_path': right_image_path,
            'mode': 'both'  # 设置模式为双眼
        }
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 60})
        
        # 运行模型预测
        results = predict_disease(processed_image, {
            'patient_id': patient_id,
            'patient_name': patient_name,
            'age': age,
            'gender': gender,
            'exam_date': exam_date
        })
        socketio.emit('analysis_update', {'status': 'processing', 'progress': 90})
        
        # 发送完成消息和结果
        socketio.emit('analysis_update', {
            'status': 'completed',
            'progress': 100,
            'results': results
        })
        
    except Exception as e:
        print(f"分析过程中发生错误: {str(e)}")
        # 发送错误消息
        socketio.emit('analysis_update', {
            'status': 'error',
            'message': str(e)
        })

def allowed_file(filename):
    """检查文件类型是否允许"""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

@app.route('/save_result', methods=['POST'])
def save_result():
    try:
        # 获取表单数据
        image = request.files.get('image')
        patient_id = request.form.get('patient_id')
        patient_name = request.form.get('patient_name')
        date_str = request.form.get('date')
        
        if not image or not patient_id or not patient_name or not date_str:
            return jsonify({'success': False, 'message': '缺少必要参数'})
        
        # 创建输出目录
        output_dir = os.path.join('output', date_str)
        os.makedirs(output_dir, exist_ok=True)
        
        # 保存图片
        filename = f"{patient_id}-{patient_name}-{datetime.now().strftime('%H%M%S')}.png"
        file_path = os.path.join(output_dir, filename)
        image.save(file_path)
        
        return jsonify({'success': True, 'file_path': file_path})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

# 添加AI问答接口
@app.route('/ai_chat', methods=['POST'])
def ai_chat():
    try:
        # 获取请求数据
        data = request.json
        user_message = data.get('message', '')
        api_key = data.get('api_key', '')
        chat_history = data.get('history', [])
        
        if not user_message:
            return jsonify({'success': False, 'message': '请输入问题'})
        
        if not api_key:
            return jsonify({'success': False, 'message': 'API Key未配置'})
        
        # 验证API Key格式
        if not api_key.startswith('sk-'):
            return jsonify({'success': False, 'message': 'API Key格式不正确'})
        
        # 构建眼科专业的系统提示词
        system_prompt = """你是一位经验丰富的眼科医生助手，专长于眼底疾病的诊断和治疗建议。
请基于专业医学知识回答用户关于眼科疾病的问题，包括但不限于：
1. 糖尿病视网膜病变、青光眼、年龄相关性黄斑变性等常见眼底疾病的症状、诊断和治疗方法
2. 眼底检查的目的、方法和注意事项
3. 眼底疾病的预防和日常护理建议
4. 眼底疾病与全身疾病的关系

请注意：
- 回答应当准确、专业且易于理解
- 对于需要就医的情况，请明确建议用户咨询专业医生
- 不要做出确定的诊断，而是提供专业的参考信息
- 如果问题超出眼科范围，请礼貌地引导用户回到眼科相关话题"""
        
        # 构建消息列表
        messages = [{"role": "system", "content": system_prompt}]
        
        # 添加聊天历史
        if chat_history:
            for msg in chat_history:
                if msg.get('role') in ['user', 'assistant'] and msg.get('content'):
                    messages.append({"role": msg['role'], "content": msg['content']})
        
        # 如果历史记录中没有当前用户消息，则添加
        if not chat_history or chat_history[-1]['role'] != 'user' or chat_history[-1]['content'] != user_message:
            messages.append({"role": "user", "content": user_message})
        
        # 构建请求数据
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
        
        payload = {
            "model": "deepseek-ai/DeepSeek-V2.5",
            "messages": messages,
            "temperature": 0.7,
            "max_tokens": 1500  # 增加token上限，处理长回复
        }
        
        # 设置请求超时
        try:
            # 调用硅基流动API
            response = requests.post(
                "https://api.siliconflow.cn/v1/chat/completions",
                headers=headers,
                json=payload,
                timeout=25  # 25秒超时
            )
            
            # 解析响应
            if response.status_code == 200:
                result = response.json()
                ai_response = result['choices'][0]['message']['content']
                return jsonify({
                    'success': True, 
                    'response': ai_response
                })
            else:
                error_message = f"API调用失败: {response.status_code} - {response.text}"
                return jsonify({'success': False, 'message': error_message})
        except requests.exceptions.Timeout:
            return jsonify({'success': False, 'message': '请求超时，请稍后再试'})
        except requests.exceptions.RequestException as e:
            return jsonify({'success': False, 'message': f'网络请求错误: {str(e)}'})
            
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)})

@app.route('/analyze_batch', methods=['POST'])
def analyze_batch():
    """处理批量图像分析请求"""
    try:
        # 获取表单数据
        patient_id = request.form.get('patient_id', '')
        patient_name = request.form.get('patient_name', '')
        age = request.form.get('age', '')
        gender = request.form.get('gender', '')
        exam_date = request.form.get('exam_date', '')
        mode = request.form.get('mode', 'single')
        
        # 检查是否使用批量患者信息
        is_batch_patient_info = request.form.get('batch_patient_info') == 'true'
        
        # 构建默认患者信息
        default_patient_info = {
            "id": patient_id or "未填写",
            "name": patient_name or "未填写", 
            "age": age or "未填写",
            "gender": gender or "未填写",
            "exam_date": exam_date or datetime.now().strftime('%Y-%m-%d')
        }
        
        # 创建任务ID
        task_id = str(uuid.uuid4())
        
        # 获取上传的文件
        if mode == 'single':
            files = request.files.getlist('files[]')
            if not files:
                return jsonify({"success": False, "message": "未找到单眼图像文件"})
            
            # 保存文件并组织文件信息，包括每个文件对应的患者信息
            file_infos = []
            
            for i, file in enumerate(files):
                if file and allowed_file(file.filename):
                    # 生成安全的文件名
                    filename = secure_filename(file.filename)
                    # 添加唯一标识符防止文件名冲突
                    unique_filename = f"{uuid.uuid4().hex}_{filename}"
                    filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
                    file.save(filepath)
                    
                    # 为每个文件创建患者信息
                    # 如果启用了批量患者信息，尝试获取此文件特定的患者信息
                    file_patient_info = default_patient_info.copy()
                    
                    if is_batch_patient_info:
                        # 检查是否有该文件的特定患者信息
                        file_id_key = f"file_{i}_patient_id"
                        file_name_key = f"file_{i}_patient_name"
                        file_gender_key = f"file_{i}_patient_gender"
                        file_age_key = f"file_{i}_patient_age"
                        
                        if file_id_key in request.form:
                            file_patient_info["id"] = request.form.get(file_id_key)
                        if file_name_key in request.form:
                            file_patient_info["name"] = request.form.get(file_name_key)
                        if file_gender_key in request.form:
                            file_patient_info["gender"] = request.form.get(file_gender_key)
                        if file_age_key in request.form:
                            file_patient_info["age"] = request.form.get(file_age_key)
                    
                    # 添加到文件信息列表
                    file_infos.append({
                        'filename': unique_filename,
                        'original_name': filename,
                        'patient_info': file_patient_info
                    })
            
            # 使用全局字典保存任务信息
            with batch_tasks_lock:
                batch_tasks[task_id] = {
                    'mode': 'single',
                    'file_infos': file_infos,  # 使用包含患者信息的文件信息列表
                    'default_patient_info': default_patient_info,
                    'progress': 0,
                    'total_files': len(file_infos),
                    'processed_files': 0
                }
                
        else:  # mode == 'both'
            left_files = request.files.getlist('left_files[]')
            right_files = request.files.getlist('right_files[]')
            
            if not left_files or not right_files:
                return jsonify({"success": False, "message": "未找到左右眼图像文件"})
            
            if len(left_files) != len(right_files):
                return jsonify({"success": False, "message": "左右眼图像文件数量不匹配"})
            
            # 保存文件并组织文件信息，包括每对文件的患者信息
            file_pair_infos = []
            
            for i in range(len(left_files)):
                left_file = left_files[i]
                right_file = right_files[i]
                
                if left_file and right_file and allowed_file(left_file.filename) and allowed_file(right_file.filename):
                    # 生成安全的文件名
                    left_filename = secure_filename(left_file.filename)
                    right_filename = secure_filename(right_file.filename)
                    
                    # 添加唯一标识符防止文件名冲突
                    unique_left = f"{uuid.uuid4().hex}_left_{left_filename}"
                    unique_right = f"{uuid.uuid4().hex}_right_{right_filename}"
                    
                    left_filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_left)
                    right_filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_right)
                    
                    left_file.save(left_filepath)
                    right_file.save(right_filepath)
                    
                    # 为每对文件创建患者信息
                    pair_patient_info = default_patient_info.copy()
                    
                    if is_batch_patient_info:
                        # 检查是否有该文件对的特定患者信息
                        pair_id_key = f"file_{i}_patient_id"
                        pair_name_key = f"file_{i}_patient_name"
                        pair_gender_key = f"file_{i}_patient_gender"
                        pair_age_key = f"file_{i}_patient_age"
                        
                        if pair_id_key in request.form:
                            pair_patient_info["id"] = request.form.get(pair_id_key)
                        if pair_name_key in request.form:
                            pair_patient_info["name"] = request.form.get(pair_name_key)
                        if pair_gender_key in request.form:
                            pair_patient_info["gender"] = request.form.get(pair_gender_key)
                        if pair_age_key in request.form:
                            pair_patient_info["age"] = request.form.get(pair_age_key)
                    
                    # 添加到文件对信息列表
                    file_pair_infos.append({
                        'left': unique_left,
                        'right': unique_right,
                        'original_left': left_filename,
                        'original_right': right_filename,
                        'patient_info': pair_patient_info
                    })
            
            # 使用全局字典保存任务信息
            with batch_tasks_lock:
                batch_tasks[task_id] = {
                    'mode': 'both',
                    'file_pair_infos': file_pair_infos,  # 使用包含患者信息的文件对信息列表
                    'default_patient_info': default_patient_info,
                    'progress': 0,
                    'total_files': len(file_pair_infos),
                    'processed_files': 0
                }
        
        # 启动后台任务处理批量分析
        socketio.start_background_task(
            process_batch_task, task_id
        )
        
        return jsonify({"success": True, "message": "批量分析已开始", "task_id": task_id})
        
    except Exception as e:
        import traceback
        print(f"批量分析错误: {str(e)}")
        print(traceback.format_exc())
        return jsonify({"success": False, "message": f"处理请求时出错: {str(e)}"})

def process_batch_task(task_id):
    """处理批量分析任务"""
    with batch_tasks_lock:
        task_info = batch_tasks.get(task_id)
    
    if not task_info:
        socketio.emit('analysis_error', {'message': '任务信息未找到'})
        return
    
    try:
        from services.predict_service import PredictService
        predict_service = PredictService()
        
        # 创建批量结果保存目录
        today = datetime.now().strftime('%Y-%m-%d')
        batch_time = datetime.now().strftime('%H-%M-%S')
        output_dir = os.path.join('output', today, f'batch_{batch_time}')
        os.makedirs(output_dir, exist_ok=True)
        
        # 发送开始消息，包含总文件数
        total_files = task_info['total_files']
        socketio.emit('analysis_progress', {
            'task_id': task_id,
            'progress': 0,
            'processed_files': 0,
            'total_files': total_files,
            'message': f'开始处理 {total_files} 个文件...'
        })
        
        results = []
        saved_files = []
        
        if task_info['mode'] == 'single':
            for i, file_info in enumerate(task_info['file_infos']):
                # 从文件信息中获取文件名和患者信息
                filename = file_info['filename']
                patient_info = file_info['patient_info']
                
                # 打印使用的患者信息以进行调试
                print(f"患者信息: {patient_info}")
                print(f"图像文件: {file_info['original_name']}")
                
                # 预测单眼图像，使用文件特定的患者信息
                result = predict_service.predict('single', patient_info, filename)
                
                # 发送每个文件的处理状态
                socketio.emit('file_processed', {
                    'task_id': task_id,
                    'file_index': i + 1,
                    'filename': file_info['original_name'],
                    'diagnosis': result.get('diagnosis', {}).get('predicted_class', 'Unknown')
                })
                
                # 保存结果图像
                if result['success']:
                    saved_path = save_batch_result_image(
                        os.path.join(app.config['UPLOAD_FOLDER'], filename),
                        result,
                        patient_info,  # 使用文件特定的患者信息
                        output_dir,
                        f"single_{i+1}"
                    )
                    result['saved_image'] = saved_path
                    saved_files.append(saved_path)
                
                results.append(result)
                
                # 更新进度
                processed_files = i + 1
                progress = int((processed_files / total_files) * 100)
                
                socketio.emit('analysis_progress', {
                    'task_id': task_id,
                    'progress': progress,
                    'processed_files': processed_files,
                    'total_files': total_files,
                    'message': f'处理图像 {processed_files}/{total_files}'
                })
                
                time.sleep(0.1)
                
        else:  # mode == 'both'
            for i, pair_info in enumerate(task_info['file_pair_infos']):
                # 从文件对信息中获取文件名和患者信息
                left_filename = pair_info['left']
                right_filename = pair_info['right']
                patient_info = pair_info['patient_info']
                
                # 打印使用的患者信息以进行调试
                print(f"患者信息: {patient_info}")
                print(f"左眼图像: {pair_info['original_left']}, 右眼图像: {pair_info['original_right']}")
                
                # 预测双眼图像，使用文件对特定的患者信息
                result = predict_service.predict(
                    'both', 
                    patient_info,  # 使用文件对特定的患者信息
                    {'left': left_filename, 'right': right_filename}
                )
                
                # 保存结果图像
                if result['success']:
                    left_path = os.path.join(app.config['UPLOAD_FOLDER'], left_filename)
                    right_path = os.path.join(app.config['UPLOAD_FOLDER'], right_filename)
                    saved_path = save_batch_result_image_both(
                        left_path, 
                        right_path,
                        result,
                        patient_info,  # 使用文件对特定的患者信息
                        output_dir,
                        f"both_{i+1}"
                    )
                    result['saved_image'] = saved_path
                    saved_files.append(saved_path)
                
                results.append(result)
                
                # 更新进度
                processed_pairs = i + 1
                progress = int((processed_pairs / total_files) * 100)
                
                socketio.emit('analysis_progress', {
                    'task_id': task_id,
                    'progress': progress,
                    'processed_files': processed_pairs,
                    'total_files': total_files,
                    'message': f'处理图像对 {processed_pairs}/{total_files}'
                })
                
                time.sleep(0.1)
        
        # 使用默认患者信息创建批量结果摘要文件
        summary_path = create_batch_summary(results, output_dir, task_info['default_patient_info'])
        
        # 发送完成消息
        socketio.emit('file_processed', {
            'task_id': task_id,
            'status': 'complete',
            'success_count': len([r for r in results if r.get('success', False)]),
            'failed_count': len([r for r in results if not r.get('success', False)]),
            'output_dir': output_dir
        })
        
        # 发送完成消息
        socketio.emit('analysis_complete', {
            'task_id': task_id,
            'results': results,
            'saved_files': saved_files,
            'summary_file': summary_path,
            'output_dir': output_dir,
            'progress': 100,
            'processed_files': total_files,
            'total_files': total_files,
            'message': f'批量分析完成，结果已保存至 {output_dir}',
            'success_count': len([r for r in results if r.get('success', False)]),
            'failed_count': len([r for r in results if not r.get('success', False)])
        })
        
        # 清理任务信息
        with batch_tasks_lock:
            if task_id in batch_tasks:
                del batch_tasks[task_id]
                
    except Exception as e:
        import traceback
        print(f"批量分析任务错误: {str(e)}")
        print(traceback.format_exc())
        
        socketio.emit('analysis_error', {
            'task_id': task_id,
            'message': f'批量分析过程中出错: {str(e)}'
        })

def save_batch_result_image(image_path, result, patient_info, output_dir, prefix):
    """为批量分析保存结果图像 - 优化版"""
    try:
        from PIL import Image, ImageDraw, ImageFont
        import numpy as np
        import matplotlib.pyplot as plt
        from io import BytesIO
        import matplotlib
        
        # 设置matplotlib参数以支持中文显示
        matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial']
        matplotlib.rcParams['axes.unicode_minus'] = False
        matplotlib.rcParams['font.size'] = 9  # 减小默认字体大小
        
        # 读取原始图像
        img = Image.open(image_path)
        
        # 固定图像尺寸，保持宽高比
        if img.width > img.height:
            new_w = 500
            new_h = int(img.height * 500 / img.width)
        else:
            new_h = 500
            new_w = int(img.width * 500 / img.height)
        
        img_resized = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
        
        # 创建新的图像，增加宽度以容纳完整的概率图表
        canvas_width = 1100  # 增加总宽度
        canvas_height = 550
        
        # 创建新的白色背景图像
        new_img = Image.new('RGB', (canvas_width, canvas_height), (255, 255, 255))
        
        # 将原图放在左侧，垂直居中
        left_margin = 20
        img_y_pos = (canvas_height - new_h) // 2
        new_img.paste(img_resized, (left_margin, img_y_pos))
        
        # 创建绘图对象
        draw = ImageDraw.Draw(new_img)
        
        # 尝试多种可能的中文字体
        fonts = [
            "simhei.ttf",
            "simsun.ttc",
            "SimSun.ttf",
            "msyh.ttc",
            "msyhbd.ttc",
            "wqy-microhei.ttc",
            "wqy-zenhei.ttc",
            "NotoSansCJK-Regular.ttc",
            "/System/Library/Fonts/PingFang.ttc"
        ]
        
        # 尝试按顺序加载字体，成功则使用该字体
        font = None
        small_font = None
        for font_name in fonts:
            try:
                font = ImageFont.truetype(font_name, 20)
                small_font = ImageFont.truetype(font_name, 16)
                print(f"成功加载字体: {font_name}")
                break
            except IOError:
                continue
        
        # 如果所有字体都失败，使用默认字体
        if font is None:
            font = ImageFont.load_default()
            small_font = ImageFont.load_default()
            print("无法加载任何中文字体，使用默认字体")
        
        # 绘制分隔线，将信息区和图像区分开
        right_section_x = left_margin + new_w + 30  # 增加右侧内容起始位置的间距
        draw.line([(right_section_x - 10, 10), (right_section_x - 10, canvas_height - 10)], 
                  fill=(200, 200, 200), width=2)
        
        # 绘制标题
        title = "眼底图像分析报告"
        title_font = font if font else ImageFont.load_default()
        draw.text((right_section_x + 10, 20), title, fill=(0, 0, 128), font=title_font)
        
        # 添加患者信息
        patient_y = 60
        patient_info_texts = [
            f"患者ID: {patient_info.get('id', '未填写')}",
            f"姓名: {patient_info.get('name', '未填写')}",
            f"性别: {patient_info.get('gender', '未填写')}",
            f"年龄: {patient_info.get('age', '未填写')}",
            f"检查日期: {patient_info.get('exam_date', '未填写')}"
        ]
        
        for text in patient_info_texts:
            draw.text((right_section_x + 10, patient_y), text, fill=(0, 0, 0), font=small_font)
            patient_y += 25
        
        # 添加诊断结果
        diagnosis_y = patient_y + 20
        draw.text((right_section_x + 10, diagnosis_y), "AI 诊断结果:", fill=(0, 0, 0), font=font)
        
        # 从结果中获取诊断信息
        if isinstance(result, dict):
            if 'diagnosis' in result and isinstance(result['diagnosis'], str):
                diagnosis = result['diagnosis']
            elif 'diagnosis' in result and isinstance(result['diagnosis'], dict) and 'predicted_class' in result['diagnosis']:
                diagnosis = str(result['diagnosis']['predicted_class'])
            else:
                diagnosis = "未知诊断结果"
        else:
            diagnosis = "无法解析的结果"
        
        # 绘制诊断结果，使用红色
        draw.text((right_section_x + 10, diagnosis_y + 30), str(diagnosis), fill=(255, 0, 0), font=font)
        
        # 从结果中获取概率信息
        probs = {}
        if isinstance(result, dict):
            if 'probabilities' in result and isinstance(result['probabilities'], dict):
                probs = result['probabilities']
            elif 'diagnosis' in result and isinstance(result['diagnosis'], dict) and 'class_probabilities' in result['diagnosis']:
                probs = result['diagnosis']['class_probabilities']
        
        # 如果有概率信息，绘制概率条形图
        if probs:
            # 添加概率图标题
            probs_title_y = diagnosis_y + 70
            draw.text((right_section_x + 10, probs_title_y), "各类别概率:", fill=(0, 0, 0), font=font)
            
            # 使用matplotlib生成概率图
            # 增加图表宽度，确保有足够空间显示所有类别
            fig, ax = plt.subplots(figsize=(6, 3.5))  # 增加图表尺寸
            fig.patch.set_facecolor('white')
            
            # 排序概率，从高到低
            sorted_probs = sorted(probs.items(), key=lambda x: x[1], reverse=True)
            classes = [item[0] for item in sorted_probs]
            values = [item[1] for item in sorted_probs]
            
            # 使用水平条形图，而不是垂直条形图，更容易显示长文本
            y_pos = np.arange(len(classes))
            bars = ax.barh(y_pos, values, color=['#3498db' if i == 0 else '#95a5a6' for i in range(len(values))])
            
            # 设置Y轴标签为类别名称
            ax.set_yticks(y_pos)
            ax.set_yticklabels(classes)
            
            # 设置图表属性
            ax.set_xlim(0, 1.0)
            ax.set_xlabel('概率')
            ax.set_title('诊断类别概率分布')
            
            # 在条上添加数值标签
            for i, bar in enumerate(bars):
                width = bar.get_width()
                ax.text(width + 0.01, bar.get_y() + bar.get_height()/2, 
                       f'{width:.2f}', ha='left', va='center')
            
            # 调整布局以确保所有元素可见
            fig.tight_layout()
            
            # 保存图表到内存
            buf = BytesIO()
            fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
            plt.close(fig)
            
            # 将图表添加到图像
            buf.seek(0)
            chart = Image.open(buf)
            chart_width, chart_height = chart.size
            
            # 将图表粘贴到右侧区域
            chart_x = right_section_x + 10
            chart_y = probs_title_y + 30
            new_img.paste(chart, (chart_x, chart_y))
        
        # 添加时间戳
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        timestamp_y = canvas_height - 30
        draw.text((canvas_width - 240, timestamp_y), f"分析时间: {timestamp}", fill=(128, 128, 128), font=small_font)
        
        # 保存结果图像
        file_timestamp = datetime.now().strftime('%H%M%S')
        patient_id = patient_info.get('id', '').replace('/', '-')
        patient_name = patient_info.get('name', '').replace('/', '-')
        
        if patient_id or patient_name:
            filename = f"{prefix}_{patient_id}_{patient_name}_{file_timestamp}.png"
        else:
            filename = f"{prefix}_诊断结果_{file_timestamp}.png"
            
        save_path = os.path.join(output_dir, filename)
        new_img.save(save_path)
        
        return save_path
    except Exception as e:
        print(f"保存批量结果图像失败: {str(e)}")
        import traceback
        print(traceback.format_exc())
        return None

def save_batch_result_image_both(left_image_path, right_image_path, result, patient_info, output_dir, prefix):
    """为批量分析保存双眼结果图像 - 优化版"""
    try:
        from PIL import Image, ImageDraw, ImageFont
        import numpy as np
        import matplotlib.pyplot as plt
        from io import BytesIO
        import matplotlib
        
        # 设置matplotlib参数以支持中文显示
        matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial']
        matplotlib.rcParams['axes.unicode_minus'] = False
        matplotlib.rcParams['font.size'] = 9  # 减小默认字体大小
        
        # 读取原始图像
        left_img = Image.open(left_image_path)
        right_img = Image.open(right_image_path)
        
        # 调整两张图片为相同大小，固定高度为400像素
        target_height = 400
        left_aspect = left_img.width / left_img.height
        right_aspect = right_img.width / right_img.height
        
        left_new_width = int(target_height * left_aspect)
        right_new_width = int(target_height * right_aspect)
        
        left_img_resized = left_img.resize((left_new_width, target_height), Image.Resampling.LANCZOS)
        right_img_resized = right_img.resize((right_new_width, target_height), Image.Resampling.LANCZOS)
        
        # 创建新的图像，采用合适布局
        # 左侧放两个眼睛图像，右侧放分析结果
        total_width = 1500  # 增加总宽度，确保足够空间显示概率图表
        canvas_height = 600
        
        # 创建新的白色背景图像
        new_img = Image.new('RGB', (total_width, canvas_height), (255, 255, 255))
        
        # 计算左右眼的位置，竖直排列
        margin = 20
        left_eye_y = margin
        right_eye_y = left_eye_y + target_height + margin
        
        # 将左右眼图像粘贴到左侧，垂直排列
        new_img.paste(left_img_resized, (margin, left_eye_y))
        new_img.paste(right_img_resized, (margin, right_eye_y))
        
        # 创建绘图对象
        draw = ImageDraw.Draw(new_img)
        
        # 尝试多种可能的中文字体
        fonts = [
            "simhei.ttf",
            "simsun.ttc",
            "SimSun.ttf",
            "msyh.ttc",
            "msyhbd.ttc",
            "wqy-microhei.ttc",
            "wqy-zenhei.ttc",
            "NotoSansCJK-Regular.ttc",
            "/System/Library/Fonts/PingFang.ttc"
        ]
        
        # 尝试按顺序加载字体，成功则使用该字体
        font = None
        small_font = None
        for font_name in fonts:
            try:
                font = ImageFont.truetype(font_name, 20)
                small_font = ImageFont.truetype(font_name, 16)
                print(f"成功加载字体: {font_name}")
                break
            except IOError:
                continue
        
        # 如果所有字体都失败，使用默认字体
        if font is None:
            font = ImageFont.load_default()
            small_font = ImageFont.load_default()
            print("无法加载任何中文字体，使用默认字体")
        
        # 标记左右眼
        draw.text((margin, left_eye_y - 25), "左眼", fill=(255, 0, 0), font=font)
        draw.text((margin, right_eye_y - 25), "右眼", fill=(255, 0, 0), font=font)
        
        # 右侧内容的起始位置，增加间距
        right_section_x = max(left_new_width, right_new_width) + margin * 3
        
        # 绘制分隔线
        draw.line([(right_section_x - 10, 10), (right_section_x - 10, canvas_height - 10)], 
                  fill=(200, 200, 200), width=2)
        
        # 绘制标题
        title = "眼底图像分析报告"
        draw.text((right_section_x + 10, 20), title, fill=(0, 0, 128), font=font)
        
        # 添加患者信息
        patient_y = 60
        patient_info_texts = [
            f"患者ID: {patient_info.get('id', '未填写')}",
            f"姓名: {patient_info.get('name', '未填写')}",
            f"性别: {patient_info.get('gender', '未填写')}",
            f"年龄: {patient_info.get('age', '未填写')}",
            f"检查日期: {patient_info.get('exam_date', '未填写')}"
        ]
        
        for text in patient_info_texts:
            draw.text((right_section_x + 10, patient_y), text, fill=(0, 0, 0), font=small_font)
            patient_y += 25
        
        # 添加诊断结果
        diagnosis_y = patient_y + 20
        draw.text((right_section_x + 10, diagnosis_y), "AI 诊断结果:", fill=(0, 0, 0), font=font)
        
        # 从结果中获取诊断信息
        if isinstance(result, dict):
            if 'diagnosis' in result and isinstance(result['diagnosis'], str):
                diagnosis = result['diagnosis']
            elif 'diagnosis' in result and isinstance(result['diagnosis'], dict) and 'predicted_class' in result['diagnosis']:
                diagnosis = str(result['diagnosis']['predicted_class'])
            else:
                diagnosis = "未知诊断结果"
        else:
            diagnosis = "无法解析的结果"
        
        # 绘制诊断结果，使用红色
        draw.text((right_section_x + 10, diagnosis_y + 30), str(diagnosis), fill=(255, 0, 0), font=font)
        
        # 从结果中获取概率信息
        probs = {}
        if isinstance(result, dict):
            if 'probabilities' in result and isinstance(result['probabilities'], dict):
                probs = result['probabilities']
            elif 'diagnosis' in result and isinstance(result['diagnosis'], dict) and 'class_probabilities' in result['diagnosis']:
                probs = result['diagnosis']['class_probabilities']
        
        # 如果有概率信息，绘制概率条形图
        if probs:
            # 添加概率图标题
            probs_title_y = diagnosis_y + 70
            draw.text((right_section_x + 10, probs_title_y), "各类别概率:", fill=(0, 0, 0), font=font)
            
            # 使用matplotlib生成概率图
            fig, ax = plt.subplots(figsize=(6, 3.5))  # 增加图表尺寸
            fig.patch.set_facecolor('white')
            
            # 排序概率，从高到低
            sorted_probs = sorted(probs.items(), key=lambda x: x[1], reverse=True)
            classes = [item[0] for item in sorted_probs]
            values = [item[1] for item in sorted_probs]
            
            # 使用水平条形图，而不是垂直条形图，更容易显示长文本
            y_pos = np.arange(len(classes))
            bars = ax.barh(y_pos, values, color=['#3498db' if i == 0 else '#95a5a6' for i in range(len(values))])
            
            # 设置Y轴标签为类别名称
            ax.set_yticks(y_pos)
            ax.set_yticklabels(classes)
            
            # 设置图表属性
            ax.set_xlim(0, 1.0)
            ax.set_xlabel('概率')
            ax.set_title('诊断类别概率分布')
            
            # 在条上添加数值标签
            for i, bar in enumerate(bars):
                width = bar.get_width()
                ax.text(width + 0.01, bar.get_y() + bar.get_height()/2, 
                       f'{width:.2f}', ha='left', va='center')
            
            # 调整布局以确保所有元素可见
            fig.tight_layout()
            
            # 保存图表到内存
            buf = BytesIO()
            fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
            plt.close(fig)
            
            # 将图表添加到图像
            buf.seek(0)
            chart = Image.open(buf)
            chart_width, chart_height = chart.size
            
            # 将图表粘贴到右侧区域
            chart_x = right_section_x + 10
            chart_y = probs_title_y + 30
            new_img.paste(chart, (chart_x, chart_y))
        
        # 添加时间戳
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        timestamp_y = canvas_height - 30
        draw.text((total_width - 240, timestamp_y), f"分析时间: {timestamp}", fill=(128, 128, 128), font=small_font)
        
        # 保存结果图像
        file_timestamp = datetime.now().strftime('%H%M%S')
        patient_id = patient_info.get('id', '').replace('/', '-')
        patient_name = patient_info.get('name', '').replace('/', '-')
        
        if patient_id or patient_name:
            filename = f"{prefix}_{patient_id}_{patient_name}_{file_timestamp}.png"
        else:
            filename = f"{prefix}_诊断结果_{file_timestamp}.png"
            
        save_path = os.path.join(output_dir, filename)
        new_img.save(save_path)
        
        return save_path
    except Exception as e:
        print(f"保存批量双眼结果图像失败: {str(e)}")
        import traceback
        print(traceback.format_exc())
        return None

def create_batch_summary(results, output_dir, patient_info):
    """创建批量分析结果摘要文件"""
    try:
        timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
        summary_filename = f"批量分析摘要_{timestamp}.txt"
        summary_path = os.path.join(output_dir, summary_filename)
        
        with open(summary_path, 'w', encoding='utf-8') as f:
            f.write("=" * 50 + "\n")
            f.write("批量眼底图像分析摘要\n")
            f.write("=" * 50 + "\n\n")
            
            f.write(f"患者信息:\n")
            f.write(f"  ID: {patient_info.get('id', '未填写')}\n")
            f.write(f"  姓名: {patient_info.get('name', '未填写')}\n")
            f.write(f"  性别: {patient_info.get('gender', '未填写')}\n")
            f.write(f"  年龄: {patient_info.get('age', '未填写')}\n")
            f.write(f"  检查日期: {patient_info.get('exam_date', '未填写')}\n\n")
            
            f.write(f"分析时间: {timestamp}\n")
            f.write(f"分析图像数量: {len(results)}\n\n")
            
            f.write("分析结果摘要:\n")
            f.write("-" * 50 + "\n")
            
            for i, result in enumerate(results):
                if result.get('success'):
                    f.write(f"图像 {i+1}:\n")
                    
                    # 获取诊断结果，处理不同结果格式
                    if 'diagnosis' in result:
                        if isinstance(result['diagnosis'], str):
                            diagnosis_text = result['diagnosis']
                        elif isinstance(result['diagnosis'], dict) and 'predicted_class' in result['diagnosis']:
                            diagnosis_text = result['diagnosis']['predicted_class']
                        else:
                            diagnosis_text = "未知诊断结果"
                    else:
                        diagnosis_text = "N/A"
                        
                    f.write(f"  诊断: {diagnosis_text}\n")
                    
                    # 获取概率信息，处理不同结果格式
                    if 'probabilities' in result and isinstance(result['probabilities'], dict):
                        probabilities = result['probabilities']
                    elif 'diagnosis' in result and isinstance(result['diagnosis'], dict) and 'class_probabilities' in result['diagnosis']:
                        probabilities = result['diagnosis']['class_probabilities']
                    else:
                        probabilities = {}
                        
                    if probabilities:
                        f.write("  各类别概率:\n")
                        for cls, prob in probabilities.items():
                            f.write(f"    {cls}: {prob:.4f}\n")
                    
                    if 'saved_image' in result:
                        f.write(f"  结果图像: {os.path.basename(result['saved_image'])}\n")
                else:
                    f.write(f"图像 {i+1}: 分析失败 - {result.get('message', '未知错误')}\n")
                
                f.write("-" * 50 + "\n")
        
        return summary_path
    except Exception as e:
        print(f"创建批量分析摘要失败: {str(e)}")
        import traceback
        print(traceback.format_exc())
        return None

if __name__ == '__main__':
    socketio.run(app, host='127.0.0.1', port=8000, debug=True)